#include "../util/structure.h"
#include "optimize.h"
typedef struct{
    int data;
    bool is_const;
}status;

#define FAIL (void *)(0x1145141919810uLL)
#define FAIL2 (status){.data=0,.is_const=false}

static inline void * encode(status x){
    if(!x.is_const) return FAIL;
    return (void *)(((LL)x.data<<32LL)|0x114514);
}

static inline status decode(void * ptr){
    if(ptr==FAIL) return FAIL2;
    return (status){.data=((LL)ptr)>>32LL,.is_const=true};
}

static inline void * merge(void * x,void * y){
    status xx=decode(x),yy=decode(y);
    if(!xx.is_const||!yy.is_const||xx.data!=yy.data) return FAIL;
    return x;
}

static inline status deal_operand(Operand x,MAP self){
    int no=x.var.no,addr=x.var.addr_status;
    if(no==0&&addr==0) return FAIL2;
    if(addr==CONSTANT_ID) return (status){.data=no,.is_const=true};
    if(addr!=0) return FAIL2;
    if(var_mark[no]) return FAIL2;
    void ** temp=map_find(self,no);
    if(!temp) return FAIL2;
    return decode(*temp);
}

static inline status deal(int op, Operand x, Operand y,MAP self){
    status a=deal_operand(x,self),b=deal_operand(y,self);
    if(op==OP_ASSIGN) return a;
    status c;
    if(!a.is_const||!b.is_const){
        if(op==OP_MUL&&((a.is_const&&a.data==0)||(b.is_const&&b.data==0))) return (status){.data=0,.is_const=true};
        if(op==OP_DIV&&a.is_const&&a.data==0) return a;
        if(op==OP_SUB&&x.var.no==y.var.no&&x.var.addr_status==y.var.addr_status) return (status){.data=0,.is_const=true};
        if(op==OP_DIV&&x.var.no==y.var.no&&x.var.addr_status==y.var.addr_status) return (status){.data=1,.is_const=true};
        return FAIL2;
    }
    c.is_const=true;
    switch(op){
        case OP_ADD: c.data=a.data+b.data;break;
        case OP_SUB: c.data=a.data-b.data;break;
        case OP_MUL: c.data=a.data*b.data;break;
        case OP_DIV: if(b.data==0) return FAIL2; c.data=a.data/b.data;break;
        default: Panic("should not reach here!");
    }
    return c;
}

bool const_merge(MAP x,MAP y){
    bool ret=false;
    FOR_MAP(iter,y){
        unsigned key=MAP_KEY(iter);
        void * new=MAP_VAL(iter,void *);
        void ** old=map_insert(x,key);
        if(!*old) *old=new,ret=true;
        else {
            void * temp=merge(*old,new);
            if(temp!=*old){
                *old=temp;
                ret=true;
            }
        }
    }
    return ret;
}

void const_statement(MAP self, operation * a){
    int op=a->op;
    Operand x=a->x,y=a->y,z=a->z;
    if(op<OP_CALL||op>OP_DIV) return;
    if(x.var.addr_status!=0) return;
    if(var_mark[x.var.no]) return;
    void ** pos=map_insert(self,x.var.no);
    if(op<=OP_READ) {*pos=FAIL;return;}
    *pos=encode(deal(op,y,z,self));
    return;
}

static inline void swap_operand(Operand * x,MAP self){
    if(x->var.addr_status==CONSTANT_ID) return;
    status xx=deal_operand(*x,self);
    if(xx.is_const){
        x->var.addr_status=CONSTANT_ID;
        x->var.no=xx.data;
    }
    return;
}

bool const_optimize(MAP self, operation * a){
    int op=a->op;
    if(op<=OP_READ||op>=OP_GOTO) return true;
    if(op>=OP_RET&&op<=OP_IF){
        swap_operand(&a->x,self);
        if(op==OP_IF){
            swap_operand(&a->y,self);
            if(a->x.var.addr_status==CONSTANT_ID&&a->y.var.addr_status==CONSTANT_ID){
                bool tp;
                int x=a->x.var.no,y=a->y.var.no;
                switch(a->info){
                    case IR_RELOP_GE: tp=(x>=y);break;
                    case IR_RELOP_LE: tp=(x<=y);break;
                    case IR_RELOP_GT: tp=(x>y); break;
                    case IR_RELOP_LT: tp=(x<y); break;
                    case IR_RELOP_EQ: tp=(x==y);break;
                    case IR_RELOP_NE: tp=(x!=y);break;
                    default: Panic("unexpected var %d",a->info);
                }
                if(!tp) return false;
                a->op=OP_GOTO;a->x=a->z;
                a->y.var.addr_status=a->y.var.no=a->z.var.addr_status=a->z.var.no=0;
                a->info=0;
            }
        }
    } 
    if(op>=OP_ASSIGN&&op<=OP_DIV){
        swap_operand(&a->y,self);
        if(op>=OP_ADD) swap_operand(&a->z,self);
        status temp=deal(op,a->y,a->z,self);
        if(temp.is_const){
            a->op=OP_ASSIGN;
            a->y.var.addr_status=CONSTANT_ID;
            a->y.var.no=temp.data;
            a->z.var.addr_status=a->z.var.no=0;
        }else{
            if(op==OP_ADD){
                if(a->y.var.no==0&&a->y.var.addr_status==CONSTANT_ID){
                    Operand temp=a->y;a->y=a->z;a->z=temp;
                }
                if(a->z.var.no==0&&a->z.var.addr_status==CONSTANT_ID){
                    a->op=OP_ASSIGN;
                    a->z.var.addr_status=0;
                }
            }
            if(op==OP_MUL){
                if(a->y.var.no>=1&&a->y.var.no<=2&&a->y.var.addr_status==CONSTANT_ID){
                    Operand temp=a->y;a->y=a->z;a->z=temp;
                }
                if(a->z.var.no==1&&a->z.var.addr_status==CONSTANT_ID){
                    a->op=OP_ASSIGN;
                    a->z.var.addr_status=0;
                    a->z.var.no=0;
                }else if(a->z.var.no==2&&a->z.var.addr_status==CONSTANT_ID){
                    a->op=OP_ADD;
                    a->z=a->y;
                }
            }
        }
    }
    return true;
}

MAP const_boundary_init(List param){
    MAP ret=new_map();
    FOR_LIST(iter,param) *map_insert(ret,LIST_GET(iter,uLL))=FAIL;
    return ret;
}
